// 深拷贝  循环引用会导致爆栈  若考虑map和set需分为可遍历，不可遍历类型进行区分

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrTag = '[object Array]';
const objTag = '[object Object]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';

const deepObjArr = [ mapTag, setTag, arrTag, objTag ];

function getType(obj) {
    return Object.prototype.toString.call(obj);
}
function newObj(obj) {
    const Constructor = obj.constructor;
    return new Constructor();
}
function cloneOtherObjType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
        return new Ctor(targe);
    case regexpTag:
        return cloneReg(targe);
    case symbolTag:
        return cloneSymbol(targe);
    default:
        return null;
    }
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function deepClone(obj, map = new WeakMap()) {
    const type = getType(obj);
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (Array.isArray(obj) && obj.length === 0) {
        return [];
    }
    if (typeof obj === 'object' && Object.keys(obj) === 0) {
        return {};
    }
    if (typeof obj === 'object' || deepObjArr.indexOf(type) === -1) {
        return cloneOtherObjType(obj, type);
    }
    if (map.get(obj)) {
        return map.get(obj);
    }
    const tempObj = newObj(obj);
    map.set(obj, tempObj);
    if (type === mapTag) {
        obj.forEach((val, key) => {
            tempObj.set(key, deepClone(val, map));
        });
    } else if (type === setTag) {
        obj.forEach(val => {
            tempObj.add(deepClone(val, map));
        });
    } else {
        for (const i in obj) {
            if (obj.hasOwnProperty(i)) {
                tempObj[i] = deepClone(obj[i], map);
            }
        }
    }
    return tempObj;
}

export default deepClone;